﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using CoreEx.Common.Implementation.Linq;
using CoreEx.Common.Linq;

namespace CoreEx.Database.Query.Linq
{
    public class SampleExpressionTranslator : ExpressionVisitor
    {

        protected StringBuilder Buffer { get; set; }
        IList paramList;

        public SampleExpressionTranslator(System.Collections.IList parameterList)
        {
            paramList = parameterList;
        }

        public virtual string Translate(Expression expression)
        {

            expression = Evaluator.PartialEval(expression);

            Buffer = new StringBuilder();
            Visit(expression);

            return this.Buffer.ToString();
        }

        protected static Expression PartialEval(Expression expression)
        {
            return Evaluator.PartialEval(expression);
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {

            Buffer.Append("(");
            this.Visit(b.Left);

            ConstantExpression rightConstant = b.Right as ConstantExpression;

            switch (b.NodeType)
            {

                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    Buffer.Append(" AND ");
                    break;

                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    Buffer.Append(" OR ");
                    break;

                case ExpressionType.Equal:

                    if (rightConstant != null && rightConstant.Value == null)
                    {
                        Buffer.Append(" IS ");
                    }
                    else
                    {
                        Buffer.Append(" = ");
                    }

                    break;

                case ExpressionType.NotEqual:

                    if (rightConstant != null && rightConstant.Value == null)
                    {
                        Buffer.Append(" IS NOT ");
                    }
                    else
                    {
                        Buffer.Append(" <> ");
                    }

                    break;

                case ExpressionType.LessThan:
                    Buffer.Append(" < ");
                    break;

                case ExpressionType.LessThanOrEqual:
                    Buffer.Append(" <= ");
                    break;

                case ExpressionType.GreaterThan:
                    Buffer.Append(" > ");
                    break;

                case ExpressionType.GreaterThanOrEqual:
                    Buffer.Append(" >= ");
                    break;

                case ExpressionType.Add:
                    Buffer.Append(" + ");
                    break;

                case ExpressionType.Subtract:
                    Buffer.Append(" - ");
                    break;

                case ExpressionType.Multiply:
                    Buffer.Append(" * ");
                    break;

                case ExpressionType.Divide:
                    Buffer.Append(" / ");
                    break;

                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));

            }

            this.Visit(b.Right);
            Buffer.Append(")");
            return b;
        }

        protected override Expression VisitConditional(ConditionalExpression c)
        {

            Buffer.Append("(CASE");

            ConditionalExpression conditional = c;

        do_when:

            Expression testExpr = conditional.Test;
            Expression trueExpr = conditional.IfTrue;
            Expression falseExpr = conditional.IfFalse;

            Buffer.Append(" WHEN ");
            Visit(testExpr);
            Buffer.Append(" THEN ");
            Visit(trueExpr);

            ConditionalExpression ifFalseAsConditional = falseExpr as ConditionalExpression;

            if (ifFalseAsConditional != null)
            {
                conditional = ifFalseAsConditional;
                goto do_when;
            }

            Buffer.Append(" ELSE ");
            Visit(falseExpr);

            Buffer.Append(" END)");

            return c;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {

            if (c.Value == null)
            {
                Buffer.Append("NULL");
            }
            else
            {

                switch (Type.GetTypeCode(c.Value.GetType()))
                {

                    case TypeCode.Boolean:
                        Buffer.Append(((bool)c.Value) ? 1 : 0);
                        break;

                    case TypeCode.Object:

                        if (c.Value.GetType().IsArray)
                        {
                            Array arr = (Array)c.Value;

                            Buffer.Append("(");

                            for (int i = 0; i < arr.Length; i++)
                            {
                                if (i > 0) Buffer.Append(", ");
                                Visit(Expression.Constant(arr.GetValue(i)));
                            }

                            Buffer.Append(")");

                            return c;
                        }

                        throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));

                    case TypeCode.Byte:
                    case TypeCode.Int16:
                    case TypeCode.Int32:
                    case TypeCode.Int64:
                    case TypeCode.SByte:
                    case TypeCode.UInt16:
                    case TypeCode.UInt32:
                    case TypeCode.UInt64:
                        Buffer.Append(c.Value);
                        break;

                    case TypeCode.Decimal:
                    case TypeCode.Double:
                    case TypeCode.DateTime:
                    case TypeCode.String:
                    default:


                        if (paramList == null)
                            throw new InvalidOperationException(string.Format("Cannot include {0} constants. Make sure you pass a non null IList to the constructor.", c.Value.GetType().Name));

                        Buffer.Append("{");
                        Buffer.Append(paramList.Count);
                        Buffer.Append("}");

                        paramList.Add(c.Value);
                        break;
                }
            }

            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {

            if (m.Expression == null) return m;

            switch (m.NodeType)
            {
                case ExpressionType.MemberAccess:
                    Buffer.Append(m.Member.Name);
                    return m;
            }

            throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {

            switch (u.NodeType)
            {

                case ExpressionType.Not:
                    Buffer.Append("NOT ");
                    this.Visit(u.Operand);
                    break;

                case ExpressionType.Quote:
                case ExpressionType.Convert:
                case ExpressionType.TypeAs:
                    this.Visit(u.Operand);
                    break;

                default:
                    throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
            }

            return u;
        }
    }

}
